/** Master Control Program.
 * This object calls the xmms2 Client functions and passes
 * notifications to the object(s) that implements the UI.
 * It also deals with any wayward processes that are too user-friendly.
 *
 * In general the MCP maintains overall state, with the Client object handling
 * xmms2 internal state such as medialib data.
 * While the current implementation is still somewhat inconsistent, the
 * design goal is such that the Client object could be any media server.
 */

#include <sys/select.h>

#include "mcp.h"
#include "client.h"
#include "configfile.h"
#include "ui.h"

//    MCP Declarations                                                    >fold>

MCP *mcp;

class UIList
    {
    friend class MCP;
    UIList *next;

    UI *ui;
    bool selected;

    UIList(UIList *list);
    };

UIList::UIList (UIList *list)
    { next = list; }

MCP::MCP ()
    {
    ui_list = 0;
    verbose = 2;
    clear_state();
    mcp = this;
    }

MCP::~MCP ()
    {
    }

void MCP::clear_state ()
    {
    playlist_position = -1;
    playlist_length = 0;
    song_position = 0;
    song_length = 0;
    total_song_length_previous = 0;
    total_song_length = 0;

    current_info = 0;
    }

// ---------------------------------------------------------------------- <fold<
//    MCP                                                                 >fold>

void MCP::logf (bool confirm, const char *format, ...)
    {
    if ( !confirm ) return;
    va_list vargs;
    va_start(vargs, format);
    vfprintf(stderr, format, vargs);
    va_end(vargs);
    }

// returns true if something was written, (for immediate use with logf)
bool MCP::log_verbose (int min_level, const char *format, ...)
    {
#ifndef DEBUG
   if ( min_level <= 10 )
#endif
    if ( verbose >= min_level )
        {
        if ( min_level > 2 )
            fprintf(stderr, "v%02d: ", min_level);
        va_list vargs;
        va_start(vargs, format);
        vfprintf(stderr, format, vargs);
        va_end(vargs);
        return true;
        }
    return false;
    }

/*
void MCP::info_message (const char *title, const char *text)
    {
    printf("Info (%s) %s\n", title, text);
    }

void MCP::error_message (const char *title, const char *text)
    {
    fprintf(stderr, "rmc-xmms2: %s: %s\n", title, text);
    }
*/

void MCP::add_UI (UI *obj)
    {
    ui_list = new UIList(ui_list);
    ui_list->ui = obj;
    }

void MCP::remove_UI (UI *obj)
    {
    if ( ui_list->ui == obj )
        {
        UIList *freeme = ui_list;
        ui_list = ui_list->next;
        delete freeme;
        }
    else
        {
        for ( UIList *o = ui_list; o; o = o->next )
            if ( o->next->ui == obj )
                {
                UIList *freeme = o->next;
                o->next = o->next->next;
                delete freeme;
                break;
                }
        }
    }

void MCP::init_config ()
    {
    char path[PATH_MAX+1];
    if ( Client::userconfdir_get(path, PATH_MAX) )
        {
        // TODO  do something like g_mkdir_with_parents here..
        //       only real reason this function exists
        cfg = new ConfigFile(path, "clients/rmc.conf");
        }
    }

void MCP::write_config ()
    {
    if ( !cfg->open_for_write() )
        return;

    cfg->add_processor(prefs->processor("CONFIG"));
    for ( UIList *o = ui_list; o; o = o->next )
        {
        cfg->add_processor(o->ui->init_processor(), false);
        cfg->add_processor(o->ui->processor());
        }
    cfg->perform_write();
    cfg->close_file();
    cfg->delete_processors();
    }

bool MCP::prestartup ()
    {
//    prefs = default_preferences();
    prefs = Preferences::default_type();
    xmms = new Client();
    init_config();
    if ( !cfg->open_for_read() )
        return false;

    // create the UI objects to possibly use, adding the default one LAST.
#ifdef UI_CAIRO
    {
    UI *new_cairoui(void);  // silly hack to avoid including ui_cairo.h
    add_UI(new_cairoui());
    }
#endif
    // TODO  add a basic text ui here as a fallback  (resizeable of course..)
    //       - some curses doodad with zxcvb commands and a rolling playlist.

    if ( cfg->is_open() )
        {
        cfg->add_processor(prefs->processor("CONFIG"));
        for ( UIList *o = ui_list; o; o = o->next )
            if ( cfg->add_processor(o->ui->init_processor(), true) )
                {
                o->selected = true;
                cfg->add_processor(o->ui->processor());
                log_verbose(5, "mcp: UI enabled in config: %s\n",
                            o->ui->label);
                }
        cfg->perform_read();
        // TODO  decide whether to exit on config file error or just soldier on
        cfg->close_file();
        cfg->delete_processors();

        for ( UIList *o = ui_list; o; o = o->next )
            if ( o->selected )
                {
                o->ui->activate();
                if ( !o->ui->active() )
                    log_verbose(5, "mcp: UI (%s) failed to activate.\n",
                                o->ui->label);
                }
        }
    else
        {
        log_verbose(5, "mcp: no config file; trying defaults:\n");

        for ( UIList *o = ui_list; o; o = o->next )
            {
            log_verbose(5, "mcp: trying UI (%s)\n", o->ui->label);
            o->ui->activate();
            if ( o->ui->active() )
                {
                if ( verbose >= 1 && verbose <= 4 )
                    log_verbose(1, "mcp: Default UI (%s)", o->ui->label);
                else
                    log_verbose(5, "... UI (%s) activated.\n", o->ui->label);
                o->selected = true;
                break;
                }
            else
                log_verbose(5, "... UI (%s) failed.\n", o->ui->label);
            }
        }

    for ( UIList *o = ui_list; o; o = o->next )
        if ( o->ui->active() )
            return true;

    log_verbose(1, "mcp: No working UI available.  Shucks.  There's always the CLI!\n");
    return false;
    }

// ---------------------------------------------------------------------- <fold<
//    Notifications                                                       >fold>

void MCP::fire_event (Event event)
    {
    for ( UIList *list = ui_list; list; list = list->next )
        list->ui->event(event);
    }

void MCP::fire_event (Event event, EventData *event_data)
    {
    for ( UIList *list = ui_list; list; list = list->next )
        list->ui->event(event, event_data);
    }

void MCP::connected_event ()
    {
    log_verbose(10, "MCP::connected\n");
    fire_event(EVENT_CONNECTED);
    }

void MCP::disconnected_event ()
    {
    clear_state();
    log_verbose(10, "MCP::disconnected\n");
    fire_event(EVENT_DISCONNECTED);
    }

void MCP::playing_event ()
    {
    log_verbose(10, "MCP::playing\n");
    fire_event(EVENT_PLAYING);
    }

void MCP::paused_event ()
    {
    log_verbose(10, "MCP::paused\n");
    fire_event(EVENT_PAUSED);
    } 
void MCP::stopped_event ()
    {
    log_verbose(10, "MCP::stopped\n");
    fire_event(EVENT_STOPPED);
    }

bool MCP::connected () { return xmms->connected(); }
bool MCP::playing () { return xmms->playing(); }
bool MCP::paused ()  { return xmms->paused(); }
bool MCP::stopped () { return xmms->stopped(); }

void MCP::playtime_event (unsigned int new_playtime)
    {
    song_position = new_playtime;
    for ( UIList *list = ui_list; list; list = list->next )
        list->ui->event(EVENT_PLAYTIME);
    }

void MCP::volume_event (unsigned int left, unsigned int right)
    {
    volume_left = left;
    volume_right = right;
    fire_event(EVENT_VOLUME);
    }

void MCP::id_event (unsigned int new_id)
    {
    current_info = xmms->get_current_info();
    song_length = 0;
    fire_event(EVENT_ID);
//    if ( current_info && current_info->id == new_id )
//        current_info_updated(current_info);
    }

void MCP::position_event (int new_pos)
    {
    // TODO if ( previousPlaytimeNeeded )
    playlist_position = new_pos;
    if ( xmms->paused() && song_position != 0 )
        playtime_event(0);
    xmms->get_playlist_playtime_to_current(&total_song_length_previous);
    mcp->log_verbose(10, "position changed: %d\n", new_pos);
    mcp->log_verbose(11, "    total len prev: %u\n", total_song_length_previous);
    fire_event(EVENT_POSITION);
    }

void MCP::current_info_updated (MediaInfo *info)
    {
    mcp->log_verbose(10, "MCP::updated mediainfo (%u)\n", info->id);
    current_info = info;
    song_length = current_info->duration;
    fire_event(EVENT_CURRENT_INFO);
    }

void MCP::playlist_event ()
    {
    mcp->log_verbose(11, "MCP::playlist updated\n");
    playlist_length = 0;
    total_song_length_previous = 0;
    total_song_length = 0;
    xmms->get_playlist_length(&playlist_length);

    if ( playlist_length > 0 )
        {
        // TODO if ( playlist info needed )
            xmms->request(Client::REQUEST_PLAYLIST_INFO);
        }
    else  // FIXME  should Client do this anyway?
        position_event(-1);

    fire_event(EVENT_PLAYLIST);
    }

void MCP::playlist_info_updated ()
    {
    xmms->get_playlist_playtime(&total_song_length);
    xmms->get_playlist_playtime_to_current(&total_song_length_previous);
    fire_event(EVENT_PLAYLIST_INFO);
    }

void MCP::picture_loaded (unsigned int id, unsigned char *data,
                          unsigned int length, string *hash)
    {
    EventData evd;
    evd.id = id;
    evd.picture.data = data;
    evd.picture.length = length;
    evd.picture.xmms_hash = hash;
    mcp->log_verbose(11, "MCP::picture data for %d\n", id);
    fire_event(EVENT_PICTURE_DATA, &evd);
    }

// ---------------------------------------------------------------------- <fold<
//    Requests / Status Inquiries                                         >fold>

unsigned int MCP::current_id ()
    { return xmms->get_current_id(); }

bool MCP::connect () { return xmms->connected(false); }

// only returns false if the command is not sent
bool MCP::command (Command cmd)
    {
    return xmms->command(cmd);
    }

void MCP::change_position (unsigned int position_ms)
    {
    // TODO  possibly delete this when xmms2 handles paused/stopped seeking
    if ( xmms->paused() || xmms->stopped() )
        {
        xmms->seek_pending(position_ms);
        command(COMMAND_PLAY);
        }
    else
        xmms->change_position(position_ms);
    }

// Always succeeds, this is simply a toggle
// FIXME  need a better name for this.  toggleSomething?
void MCP::maintain_playtime (bool enable)
    {
    xmms->request_playtime(enable);
    }

void MCP::change_volume (int change, bool absolute)
    {
    xmms->change_volume(change, change, absolute);
    }

void MCP::request_picture (unsigned int id)  //, const char *type?)
    {
    xmms->request_picture(id);
    }

bool MCP::muted ()
    {
    return xmms->muted();
    }
/*
void MCP::playtimeUpdateChanged ()
    {
    bool update = false;
    for ( UIList *list = ui_list; list; list = list->next )
        if ( list->ui->update_playtime )
            {
            update = true;
            break;
            }
    xmms->enablePlaytime(update);
    }

void MCP::songinfoUpdateChanged ()
    {
    if ( !song_info )
        for ( UIList *list = ui_list; list; list = list->next )
            if ( list->ui->update_song_info )
                {
                xmms->request(REQUEST_CURRENT_INFO);
                break;
                }
    }
*/

#if 0
    sigset_t sig_mask;

-- Assuming that signal masking is unneeded with a zero timeout
    sigemptyset(&sig_mask);
    sigaddset(&sig_mask, SIGINT);
    sigaddset(&sig_mask, SIGPIPE);
    sigaddset(&sig_mask, SIGALRM);
    sigaddset(&sig_mask, SIGTERM);
/*      I don't remember why I added these two..
    sigaddset(&sig_mask, SIGURG);
    sigaddset(&sig_mask, SIGXCPU);
*/
    sigprocmask(SIG_SETMASK, &sig_mask, &sig_old);
    .. select
    sigprocmask(SIG_SETMASK, &sig_old, 0);
#endif

// ---------------------------------------------------------------------- <fold<
//    Main loop                                                           >fold>

#include <signal.h>

//    struct timeval zerocool;
//        zerocool.tv_sec = 0; zerocool.tv_usec = 0;

void MCP::mainloop ()
    {
    int     nfds;
    fd_set  fd_input,
            fd_output,
            fd_exception;
    UIList *next_ui;
    struct timeval *use_timeout,
                   timeout;

    signal(SIGWINCH, SIG_IGN);
    signal(SIGUSR1, SIG_IGN);
    signal(SIGUSR2, SIG_IGN);
    signal(SIGHUP, SIG_IGN);

    for ( ;; )
        {
        nfds = 0;
        FD_ZERO(&fd_input);
        FD_ZERO(&fd_output);
        FD_ZERO(&fd_exception);
        use_timeout = 0;

        if ( xmms->connected() )
            {
            int tmp = xmms->descriptor();
            if ( tmp > nfds )
                nfds = tmp;
            if ( xmms->output_pending() )
                FD_SET(tmp, &fd_output);
            FD_SET(tmp, &fd_input);
            FD_SET(tmp, &fd_exception);
            if ( xmms->timer_wanted() )
                use_timeout = &timeout;
            }

        for ( UIList *o = ui_list; o; o = o->next )
            {
            if ( o->ui->timer_wanted() )
                use_timeout = &timeout;
            for ( int i = 0; i < o->ui->fd_count; i++ )
                {
                if ( o->ui->fd[i] > nfds )
                    nfds = o->ui->fd[i];
                if ( o->ui->output_pending() )
                    FD_SET(o->ui->fd[i], &fd_output);
                FD_SET(o->ui->fd[i], &fd_input);
                FD_SET(o->ui->fd[i], &fd_exception);
                }
            }

        if ( use_timeout )
            {
            use_timeout->tv_sec = 0;
            use_timeout->tv_usec = 20000;  // every 1/5 seconds
            }
        nfds = select(nfds + 1, &fd_input, &fd_output, &fd_exception,
                      use_timeout);

        if ( nfds == -1 )
            {
            fprintf(stderr, "select() error, hmm.. (%s)\n", strerror(errno));
            return;
            }
        else if ( nfds == 0 )
            {
            if ( use_timeout )
                {
                if ( xmms->timer_wanted() )
                    xmms->timer();
                for ( UIList *o = ui_list; o; o = next_ui )
                    {
                    next_ui = o->next;
                    if ( o->ui->timer_wanted() )
                        o->ui->timer();
                    }
                }
#ifdef DEBUG
            else  // this shouldn't happen...
                fprintf(stderr, "select() with no timeout returned 0 fd's?\n");
#endif
            }
        else
            {
            if ( xmms->connected() )
                {
                int tmp = xmms->descriptor();
                if ( FD_ISSET(tmp, &fd_input) )
                    xmms->process_input();
                if ( FD_ISSET(tmp, &fd_output) )
                    xmms->process_output();
                if ( FD_ISSET(tmp, &fd_exception) )
                    xmms->disconnect();
                }

            for ( UIList *o = ui_list; o; o = next_ui )
                {
                next_ui = o->next;
                for ( int i = 0; i < o->ui->fd_count; i++ )
                    {
                    int tmp = o->ui->fd[i];
                    if ( FD_ISSET(tmp, &fd_input) )
                        o->ui->process_input(tmp);
                    if ( FD_ISSET(tmp, &fd_output) )
                        o->ui->process_output(tmp);
                    if ( FD_ISSET(tmp, &fd_exception) )
                        o->ui->fd_exception(tmp);
                    }
                o->ui->post_event_cleanup();
                }
            }
        }
    }

// ---------------------------------------------------------------------- <fold<

// TODO  some basic command line arguments
int main (int argc, char *argv[])
    {
    mcp = new MCP();
mcp->verbose = 11;

    if ( mcp->prestartup() )
        mcp->mainloop();

    delete mcp;
    exit(0);
    }

